/*++

Copyright (c) 2004 - 2010, Intel Corporation. All rights reserved.<BR>
This program and the accompanying materials                          
are licensed and made available under the terms and conditions of the BSD License         
which accompanies this distribution.  The full text of the license may be found at        
http://opensource.org/licenses/bsd-license.php                                            
                                                                                          
THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,                     
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.             

Module Name:

  StringDB.c

Abstract:

  String database implementation
  
--*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>  // for tolower()
#include "Tiano.h"
#include "EfiUtilityMsgs.h"
#include "StrGather.h"
#include "StringDb.h"
#include "EfiInternalFormRepresentation.h"

#include EFI_PROTOCOL_DEFINITION (Hii)

typedef CHAR16  WCHAR;
#define STRING_OFFSET RELOFST

#define STRING_DB_KEY (('S' << 24) | ('D' << 16) | ('B' << 8) | 'K')
//
// Version supported by this tool
//
#define STRING_DB_VERSION             0x00010000

#define STRING_DB_MAJOR_VERSION_MASK  0xFFFF0000
#define STRING_DB_MINOR_VERSION_MASK  0x0000FFFF

#define DEFINE_STR                    L"// #define"

#define LANGUAGE_CODE_WIDTH           4
//
// This is the header that gets written to the top of the
// output binary database file.
//
typedef struct {
  UINT32  Key;
  UINT32  HeaderSize;
  UINT32  Version;
  UINT32  NumStringIdenfiers;
  UINT32  StringIdentifiersSize;
  UINT32  NumLanguages;
} STRING_DB_HEADER;

//
// When we write out data to the database, we have a UINT16 identifier, which
// indicates what follows, followed by the data. Here's the structure.
//
typedef struct {
  UINT16  DataType;
  UINT16  Reserved;
} DB_DATA_ITEM_HEADER;

#define DB_DATA_TYPE_INVALID              0x0000
#define DB_DATA_TYPE_STRING_IDENTIFIER    0x0001
#define DB_DATA_TYPE_LANGUAGE_DEFINITION  0x0002
#define DB_DATA_TYPE_STRING_DEFINITION    0x0003
#define DB_DATA_TYPE_LAST                 DB_DATA_TYPE_STRING_DEFINITION

//
// We have to keep track of a list of languages, each of which has its own
// list of strings. Define a structure to keep track of all languages and
// their list of strings.
//
typedef struct _STRING_LIST {
  struct _STRING_LIST *Next;
  UINT32              Size;         // number of bytes in string, including null terminator
  WCHAR               *LanguageName;
  WCHAR               *StringName;  // for example STR_ID_TEXT1
  WCHAR               *Scope;       //
  WCHAR               *Str;         // the actual string
  UINT16              Flags;        // properties of this string (used, undefined)
} STRING_LIST;

typedef struct _LANGUAGE_LIST {
  struct _LANGUAGE_LIST *Next;
  WCHAR                 LanguageName[4];
  WCHAR                 *PrintableLanguageName;
  STRING_LIST           *String;
  STRING_LIST           *LastString;
} LANGUAGE_LIST;

//
// We also keep track of all the string identifier names, which we assign unique
// values to. Create a structure to keep track of them all.
//
typedef struct _STRING_IDENTIFIER {
  struct _STRING_IDENTIFIER *Next;
  UINT32                    Index;  // only need 16 bits, but makes it easier with UINT32
  WCHAR                     *StringName;
  UINT16                    Flags;  // if someone referenced it via STRING_TOKEN()
} STRING_IDENTIFIER;
//
// Keep our globals in this structure to be as modular as possible.
//
typedef struct {
  FILE              *StringDBFptr;
  LANGUAGE_LIST     *LanguageList;
  LANGUAGE_LIST     *LastLanguageList;
  LANGUAGE_LIST     *CurrentLanguage;         // keep track of the last language they used
  STRING_IDENTIFIER *StringIdentifier;
  STRING_IDENTIFIER *LastStringIdentifier;
  UINT8             *StringDBFileName;
  UINT32            NumStringIdentifiers;
  UINT32            NumStringIdentifiersReferenced;
  STRING_IDENTIFIER *CurrentStringIdentifier; // keep track of the last string identifier they added
  WCHAR             *CurrentScope;
} STRING_DB_DATA;

static STRING_DB_DATA mDBData;

static const char     *mSourceFileHeader[] = {
  "//",
  "//  DO NOT EDIT -- auto-generated file",
  "//",
  "//  This file is generated by the string gather utility",
  "//",
  NULL
};

static
STRING_LIST           *
StringDBFindString (
  WCHAR                       *LanguageName,
  WCHAR                       *StringName,
  WCHAR                       *Scope,
  WCHAR_STRING_LIST           *LanguagesOfInterest,
  WCHAR_MATCHING_STRING_LIST  *IndirectionList
  );

static
STRING_IDENTIFIER     *
StringDBFindStringIdentifierByName (
  WCHAR *Name
  );

static
STRING_IDENTIFIER     *
StringDBFindStringIdentifierByIndex (
  UINT32    Index
  );

static
LANGUAGE_LIST         *
StringDBFindLanguageList (
  WCHAR *LanguageName
  );

static
void
StringDBWriteStandardFileHeader (
  FILE *OutFptr
  );

static
WCHAR                 *
AsciiToWchar (
  INT8 *Str
  );

static
WCHAR                 *
DuplicateString (
  WCHAR   *Str
  );

static
STATUS
StringDBWriteStringIdentifier (
  FILE                *DBFptr,
  UINT16              StringId,
  UINT16              Flags,
  WCHAR               *IdentifierName
  );

static
STATUS
StringDBReadStringIdentifier (
  FILE                *DBFptr
  );

static
STATUS
StringDBWriteLanguageDefinition (
  FILE            *DBFptr,
  WCHAR           *LanguageName,
  WCHAR           *PrintableLanguageName
  );

static
STATUS
StringDBReadLanguageDefinition (
  FILE            *DBFptr
  );

static
STATUS
StringDBWriteString (
  FILE            *DBFptr,
  UINT16          Flags,
  WCHAR           *Language,
  WCHAR           *StringName,
  WCHAR           *Scope,
  WCHAR           *Str
  );

static
STATUS
StringDBReadString (
  FILE            *DBFptr
  );

static
STATUS
StringDBReadGenericString (
  FILE      *DBFptr,
  UINT16    *Size,
  WCHAR     **Str
  );

static
STATUS
StringDBWriteGenericString (
  FILE      *DBFptr,
  WCHAR     *Str
  );

static
void
StringDBAssignStringIndexes (
  VOID
  );

/*****************************************************************************/

/*++

Routine Description:
  Constructor function for the string database handler.

Arguments:
  None.

Returns:
  None.

--*/
void
StringDBConstructor (
  VOID
  )
{
  memset ((char *) &mDBData, 0, sizeof (STRING_DB_DATA));
  mDBData.CurrentScope = DuplicateString (L"NULL");
}

/*****************************************************************************/

/*++

Routine Description:
  Destructor function for the string database handler.

Arguments:
  None.

Returns:
  None.

--*/
void
StringDBDestructor (
  VOID
  )
{
  LANGUAGE_LIST     *NextLang;
  STRING_LIST       *NextStr;
  STRING_IDENTIFIER *NextIdentifier;
  //
  // Close the database file if it's open
  //
  if (mDBData.StringDBFptr != NULL) {
    fclose (mDBData.StringDBFptr);
    mDBData.StringDBFptr = NULL;
  }
  //
  // If we've allocated any strings/languages, free them up
  //
  while (mDBData.LanguageList != NULL) {
    NextLang = mDBData.LanguageList->Next;
    //
    // Free up all strings for this language
    //
    while (mDBData.LanguageList->String != NULL) {
      NextStr = mDBData.LanguageList->String->Next;
      FREE (mDBData.LanguageList->String->Str);
      FREE (mDBData.LanguageList->String);
      mDBData.LanguageList->String = NextStr;
    }

    FREE (mDBData.LanguageList->PrintableLanguageName);
    FREE (mDBData.LanguageList);
    mDBData.LanguageList = NextLang;
  }
  //
  // Free up string identifiers
  //
  while (mDBData.StringIdentifier != NULL) {
    NextIdentifier = mDBData.StringIdentifier->Next;
    FREE (mDBData.StringIdentifier->StringName);
    FREE (mDBData.StringIdentifier);
    mDBData.StringIdentifier = NextIdentifier;
  }
  //
  // Free the filename
  //
  if (mDBData.StringDBFileName != NULL) {
    FREE (mDBData.StringDBFileName);
    mDBData.StringDBFileName = NULL;
  }
  //
  // We save a copy of the scope, so free it up if we
  // have one.
  //
  if (mDBData.CurrentScope != NULL) {
    FREE (mDBData.CurrentScope);
    mDBData.CurrentScope = NULL;
  }
}

/*****************************************************************************/

/*++

Routine Description:

  Dump the contents of a database to an output C file.

Arguments:

  FileName        - name of the output file to write 
  BaseName        - used for the name of the C array defined
  Languages       - list of languages of interest

Returns:

  STATUS

Notes:

  Languages is a pointer to a linked list of languages specified on
  the command line. Format is "eng" and "spa+cat". For this, print
  the strings for eng. Print the strings for spa too, but if one is
  missing look for a cat string and print if it it exists.

--*/
STATUS
StringDBDumpCStrings (
  INT8                        *FileName,
  INT8                        *BaseName,
  WCHAR_STRING_LIST           *LanguagesOfInterest,
  WCHAR_MATCHING_STRING_LIST  *IndirectionList
  )
{
  FILE                        *Fptr;
  LANGUAGE_LIST               *Lang;
  STRING_LIST                 *CurrString;
  STRING_LIST                 EmptyString;
  UINT32                      Offset;
  UINT32                      StringIndex;
  UINT32                      TempIndex;
  UINT32                      BytesThisLine;
  EFI_HII_STRING_PACK_HEADER  StringPack;
  UINT8                       *Ptr;
  UINT32                      Len;
  WCHAR                       ZeroString[1];
  WCHAR_STRING_LIST           *LOIPtr;
  BOOLEAN                     LanguageOk;
  WCHAR                       *TempStringPtr;
  WCHAR                       *LangName;
  STRING_IDENTIFIER           *StringIdentifier;

  if ((Fptr = fopen (FileName, "w")) == NULL) {
    Error (NULL, 0, 0, FileName, "failed to open output C string file");
    return STATUS_ERROR;
  }
  //
  // Assign index values to the string identifiers
  //
  StringDBAssignStringIndexes ();
  //
  // Write the standard header to the output file, then the structure
  // definition header.
  //
  StringDBWriteStandardFileHeader (Fptr);
  fprintf (Fptr, "\nunsigned char %s[] = {\n", BaseName);
  //
  // If a given string is not defined, then we'll use this one.
  //
  memset (&EmptyString, 0, sizeof (EmptyString));
  EmptyString.Size  = sizeof (ZeroString);
  EmptyString.Str   = ZeroString;
  //
  // Process each language, then each string for each langage
  //
  ZeroString[0] = 0;
  for (Lang = mDBData.LanguageList; Lang != NULL; Lang = Lang->Next) {
    //
    // If we have a language list, then make sure this language is in that
    // list.
    //
    LanguageOk  = TRUE;
    LangName    = Lang->LanguageName;
    if (LanguagesOfInterest != NULL) {
      LanguageOk = FALSE;
      for (LOIPtr = LanguagesOfInterest; LOIPtr != NULL; LOIPtr = LOIPtr->Next) {
        if (wcsncmp (LOIPtr->Str, Lang->LanguageName, LANGUAGE_IDENTIFIER_NAME_LEN) == 0) {
          LangName    = LOIPtr->Str;
          LanguageOk  = TRUE;
          break;
        }
      }
    }

    if (!LanguageOk) {
      continue;
    }
    //
    // Process each string for this language. We have to make 3 passes on the strings:
    //   Pass1: computes sizes and fill in the string pack header
    //   Pass2: write the array of offsets
    //   Pass3: write the strings
    //
    //
    // PASS 1: Fill in and print the HII string pack header
    //
    // Compute the size for this language package and write
    // the header out. Each string package contains:
    //   Header
    //   Offset[]  -- an array of offsets to strings, of type RELOFST each
    //   String[]  -- the actual strings themselves
    //
    fprintf (
      Fptr,
      "\n//******************************************************************************"
      "\n// Start of string definitions for %S/%S",
      Lang->LanguageName,
      Lang->PrintableLanguageName
      );
    memset ((char *) &StringPack, 0, sizeof (EFI_HII_STRING_PACK_HEADER));
    StringPack.Header.Type        = EFI_HII_STRING;
    StringPack.NumStringPointers  = (UINT16) mDBData.NumStringIdentifiersReferenced;
    //
    // First string is the language name. If we're printing all languages, then
    // it's just the "spa". If we were given a list of languages to print, then it's
    // the "spacat" string. Compute its offset and fill in
    // the info in the header. Since we know the language name string's length,
    // and the printable language name follows it, use that info to fill in the
    // entry for the printable language name as well.
    //
    StringPack.LanguageNameString = (STRING_OFFSET) (sizeof (EFI_HII_STRING_PACK_HEADER) + (mDBData.NumStringIdentifiersReferenced * sizeof (STRING_OFFSET)));
    StringPack.PrintableLanguageName = (STRING_OFFSET) (StringPack.LanguageNameString + (wcslen (LangName) + 1) * sizeof (WCHAR));
    //
    // Add up the size of all strings so we can fill in our header.
    //
    Len = 0;
    for (StringIndex = 0; StringIndex < mDBData.NumStringIdentifiersReferenced; StringIndex++) {
      //
      // For the first string (language name), we print out the "spacat" if they
      // requested it. We set LangName to point to the proper language name string above.
      //
      if (StringIndex == STRING_ID_LANGUAGE_NAME) {
        Len += (wcslen (LangName) + 1) * sizeof (WCHAR);
      } else {
        //
        // Find a string with this language.stringname
        //
        StringIdentifier = StringDBFindStringIdentifierByIndex (StringIndex);
        if (StringIdentifier == NULL) {
          Error (NULL, 0, 0, "internal error", "invalid string index 0x%X", StringIndex);
          return STATUS_ERROR;
        }
        //
        // Find a matching string if this string identifier was referenced
        //
        EmptyString.Flags = STRING_FLAGS_UNDEFINED;
        CurrString        = NULL;
        if (StringIdentifier->Flags & STRING_FLAGS_REFERENCED) {
          CurrString = StringDBFindString (
                        Lang->LanguageName,
                        StringIdentifier->StringName,
                        NULL,
                        LanguagesOfInterest,
                        IndirectionList
                        );
          if (NULL == CurrString) {
            //
            // If string for Lang->LanguageName is not found, try to get an English version
            //
            CurrString = StringDBFindString (
                          L"eng",
                          StringIdentifier->StringName,
                          NULL,
                          LanguagesOfInterest,
                          IndirectionList
                          );
          }
        }

        if (CurrString == NULL) {
          CurrString = &EmptyString;
          EmptyString.Flags |= StringIdentifier->Flags;
        }

        Len += CurrString->Size;
      }
    }
    StringPack.Header.Length =    sizeof (EFI_HII_STRING_PACK_HEADER) 
                                + mDBData.NumStringIdentifiersReferenced * sizeof (STRING_OFFSET) 
                                + Len;
    //
    // Write out the header one byte at a time
    //
    Ptr = (UINT8 *) &StringPack;
    for (TempIndex = 0; TempIndex < sizeof (EFI_HII_STRING_PACK_HEADER); TempIndex++, Ptr++) {
      if ((TempIndex & 0x07) == 0) {
        fprintf (Fptr, "\n  ");
      }

      fprintf (Fptr, "0x%02X, ", (UINT32) *Ptr);
    }

    fprintf (Fptr, "\n  // offset 0x%X\n", sizeof (StringPack));
    //
    // PASS2 : write the offsets
    //
    // Traverse the list of strings again and write the array of offsets. The
    // offset to the first string is the size of the string pack header
    // plus the size of the offsets array. The other strings follow it.
    //
    StringIndex = 0;
    Offset      = sizeof (StringPack) + mDBData.NumStringIdentifiersReferenced * sizeof (STRING_OFFSET);
    for (StringIndex = 0; StringIndex < mDBData.NumStringIdentifiersReferenced; StringIndex++) {
      //
      // Write the offset, followed by a useful comment
      //
      fprintf (Fptr, "  ");
      Ptr = (UINT8 *) &Offset;
      for (TempIndex = 0; TempIndex < sizeof (STRING_OFFSET); TempIndex++) {
        fprintf (Fptr, "0x%02X, ", (UINT32) Ptr[TempIndex]);
      }
      //
      // Find the string name
      //
      StringIdentifier = StringDBFindStringIdentifierByIndex (StringIndex);
      if (StringIdentifier == NULL) {
        Error (NULL, 0, 0, "internal error", "invalid string index 0x%X", StringIndex);
        return STATUS_ERROR;
      }

      fprintf (Fptr, " // offset to string %S (0x%04X)", StringIdentifier->StringName, StringIndex);
      //
      // For the first string (language name), we print out the "spacat" if they
      // requested it. We set LangName to point to the proper language name string above.
      //
      if (StringIndex == STRING_ID_LANGUAGE_NAME) {
        Offset += (wcslen (LangName) + 1) * sizeof (WCHAR);
        CurrString = StringDBFindString (
                      Lang->LanguageName,
                      StringIdentifier->StringName,
                      NULL, // scope
                      NULL,
                      NULL
                      );
      } else {
        //
        // Find a matching string
        //
        CurrString = StringDBFindString (
                      Lang->LanguageName,
                      StringIdentifier->StringName,
                      NULL,   // scope
                      LanguagesOfInterest,
                      IndirectionList
                      );

        if (NULL == CurrString) {
          CurrString = StringDBFindString (
                        L"eng",
                        StringIdentifier->StringName,
                        NULL, // scope
                        LanguagesOfInterest,
                        IndirectionList
                        );
        }

        EmptyString.LanguageName = Lang->LanguageName;
        if (CurrString == NULL) {
          CurrString        = &EmptyString;
          EmptyString.Flags = STRING_FLAGS_UNDEFINED;
        } else if ((StringIdentifier->Flags & STRING_FLAGS_REFERENCED) == 0) {
          CurrString        = &EmptyString;
          EmptyString.Flags = 0;
        }

        Offset += CurrString->Size;
      }
      //
      // Print useful info about this string
      //
      if ((StringIdentifier->Flags & STRING_FLAGS_REFERENCED) == 0) {
        fprintf (Fptr, " - not referenced");
      }

      if (CurrString->Flags & STRING_FLAGS_UNDEFINED) {
        fprintf (Fptr, " - not defined for this language");
      } else if (wcscmp (CurrString->LanguageName, Lang->LanguageName) != 0) {
        fprintf (
          Fptr,
          " - not defined for this language -- using secondary language %S definition",
          CurrString->LanguageName
          );
      }

      fprintf (Fptr, "\n");
    }
    //
    // For unreferenced string identifiers, print a message that they are not referenced anywhere
    //
    while (StringIndex < mDBData.NumStringIdentifiers) {
      StringIdentifier = StringDBFindStringIdentifierByIndex (StringIndex);
      if (StringIdentifier != NULL) {
        fprintf (Fptr, "  // %S not referenced\n", StringIdentifier->StringName);
      }

      StringIndex++;
    }

    //
    // PASS 3: write the strings themselves.
    // Keep track of how many bytes we write per line because some editors
    // (Visual Studio for instance) can't handle too long of lines.
    //
    Offset = sizeof (StringPack) + mDBData.NumStringIdentifiersReferenced * sizeof (STRING_OFFSET);
    for (StringIndex = 0; StringIndex < mDBData.NumStringIdentifiersReferenced; StringIndex++) {
      StringIdentifier = StringDBFindStringIdentifierByIndex (StringIndex);
      if (StringIdentifier == NULL) {
        Error (NULL, 0, 0, "internal error", "invalid string index 0x%X", StringIndex);
        return STATUS_ERROR;
      }

      fprintf (Fptr, "  // string %S offset 0x%08X\n  ", StringIdentifier->StringName, Offset);
      //
      // For the first string (language name), we print out the "spacat" if they
      // requested it. We set LangName to point to the proper language name string above.
      //
      if (StringIndex == STRING_ID_LANGUAGE_NAME) {
        TempStringPtr = LangName;
      } else {
        //
        // Find a matching string if this string identifier was referenced
        //
        CurrString = NULL;
        if (StringIdentifier->Flags & STRING_FLAGS_REFERENCED) {
          CurrString = StringDBFindString (
                        Lang->LanguageName,
                        StringIdentifier->StringName,
                        NULL,   // scope
                        LanguagesOfInterest,
                        IndirectionList
                        );
          if (NULL == CurrString) {
            CurrString = StringDBFindString (
                          L"eng",
                          StringIdentifier->StringName,
                          NULL, // scope
                          LanguagesOfInterest,
                          IndirectionList
                          );
          }
        }

        if (CurrString == NULL) {
          CurrString = &EmptyString;
        }

        TempStringPtr = CurrString->Str;
      }

      BytesThisLine = 0;
      for (TempIndex = 0; TempStringPtr[TempIndex] != 0; TempIndex++) {
        fprintf (
          Fptr,
          "0x%02X, 0x%02X, ",
          (UINT32) TempStringPtr[TempIndex] & 0xFF,
          (UINT32) ((TempStringPtr[TempIndex] >> 8) & 0xFF)
          );
        BytesThisLine += 2;
        Offset += 2;
        //
        // Let's say we only allow 14 per line
        //
        if (BytesThisLine > 14) {
          fprintf (Fptr, "\n  ");
          BytesThisLine = 0;
        }
      }
      //
      // Print NULL WCHAR at the end of this string.
      //
      fprintf (Fptr, "0x00, 0x00,\n");
      Offset += 2;
    }
    //
    // Sanity check the offset. Make sure our running offset is what we put in the
    // string pack header.
    //
    if (StringPack.Header.Length != Offset) {
      Error (
        __FILE__,
        __LINE__,
        0,
        "application error",
        "stringpack size 0x%X does not match final size 0x%X",
        StringPack.Header.Length,
        Offset
        );
    }
  }
  //
  // Print terminator string pack, closing brace and close the file.
  // The size of 0 triggers to the consumer that this is the end.
  //
  memset ((char *) &StringPack, 0, sizeof (EFI_HII_STRING_PACK_HEADER));
  StringPack.Header.Type  = EFI_HII_STRING;
  Ptr                     = (UINT8 *) &StringPack;
  fprintf (Fptr, "\n  // strings terminator pack");
  for (TempIndex = 0; TempIndex < sizeof (StringPack); TempIndex++, Ptr++) {
    if ((TempIndex & 0x0F) == 0) {
      fprintf (Fptr, "\n  ");
    }

    fprintf (Fptr, "0x%02X, ", (UINT32) *Ptr);
  }

  fprintf (Fptr, "\n};\n");
  fclose (Fptr);
  return STATUS_SUCCESS;
}

/*****************************************************************************/

/*++

Routine Description:

  Dump the #define string names

Arguments:

  FileName        - name of the output file to write 
  BaseName        - used for the protection #ifndef/#endif 

Returns:

  STATUS

--*/
STATUS
StringDBDumpStringDefines (
  INT8 *FileName,
  INT8 *BaseName
  )
{
  FILE              *Fptr;
  STRING_IDENTIFIER *Identifier;
  INT8              CopyBaseName[100];
  UINT32            Index;
  const INT8        *StrDefHeader[] = {
    "#ifndef _%s_STRINGS_DEFINE_H_\n",
    "#define _%s_STRINGS_DEFINE_H_\n\n",
    NULL
  };

  if ((Fptr = fopen (FileName, "w")) == NULL) {
    Error (NULL, 0, 0, FileName, "failed to open output string defines file");
    return STATUS_ERROR;
  }
  //
  // Get the base source filename and convert to uppercase.
  //
  if (sizeof (CopyBaseName) <= strlen (BaseName) + 1) {
    Error (NULL, 0, 0, "application error", "StringDBDumpStringDefines() string length insufficient");
    return STATUS_ERROR;
  }

  strcpy (CopyBaseName, BaseName);
  for (Index = 0; CopyBaseName[Index] != 0; Index++) {
    if (islower (CopyBaseName[Index])) {
      CopyBaseName[Index] = (INT8) toupper (CopyBaseName[Index]);
    }
  }
  //
  // Assign index values to the string identifiers
  //
  StringDBAssignStringIndexes ();
  //
  // Write the standard header to the output file, and then the
  // protective #ifndef.
  //
  StringDBWriteStandardFileHeader (Fptr);
  for (Index = 0; StrDefHeader[Index] != NULL; Index++) {
    fprintf (Fptr, StrDefHeader[Index], CopyBaseName);
  }
  //
  // Print all the #defines for the string identifiers. Print identifiers
  // whose names start with '$' as comments. Add comments for string
  // identifiers not used as well.
  //
  Identifier = mDBData.StringIdentifier;
  while (Identifier != NULL) {
    if (Identifier->StringName[0] == L'$') {
      fprintf (Fptr, "// ");
    }

    if (Identifier->Flags & STRING_FLAGS_REFERENCED) {
      fprintf (Fptr, "#define %-40S 0x%04X\n", Identifier->StringName, Identifier->Index);
    } else {
      fprintf (Fptr, "//#define %-40S 0x%04X // not referenced\n", Identifier->StringName, Identifier->Index);
    }

    Identifier = Identifier->Next;
  }

  fprintf (Fptr, "\n#endif\n");
  fclose (Fptr);
  return STATUS_SUCCESS;
}

/*****************************************************************************/

/*++

Routine Description:

  Add a string identifier to the database.

Arguments:

  StringName      - name of the string identifier. For example "STR_MY_STRING"
  NewId           - if an ID has been assigned
  Flags           - characteristics for the identifier

Returns:

  STATUS

--*/
STATUS
StringDBAddStringIdentifier (
  WCHAR     *StringName,
  UINT16    *NewId,
  UINT16    Flags
  )
{
  STRING_IDENTIFIER *StringIdentifier;
  STATUS            Status;
  //
  // If it was already used for some other language, then we don't
  // need to add it. But set it to the current string identifier.
  // The referenced bit is sticky.
  //
  Status            = STATUS_SUCCESS;
  StringIdentifier  = StringDBFindStringIdentifierByName (StringName);
  if (StringIdentifier != NULL) {
    if (Flags & STRING_FLAGS_REFERENCED) {
      StringIdentifier->Flags |= STRING_FLAGS_REFERENCED;
    }

    mDBData.CurrentStringIdentifier = StringIdentifier;
    *NewId                          = (UINT16) StringIdentifier->Index;
    return Status;
  }

  StringIdentifier = (STRING_IDENTIFIER *) MALLOC (sizeof (STRING_IDENTIFIER));
  if (StringIdentifier == NULL) {
    Error (NULL, 0, 0, NULL, "memory allocation error");
    return STATUS_ERROR;
  }

  memset ((char *) StringIdentifier, 0, sizeof (STRING_IDENTIFIER));
  StringIdentifier->StringName = (WCHAR *) malloc ((wcslen (StringName) + 1) * sizeof (WCHAR));
  if (StringIdentifier->StringName == NULL) {
    Error (NULL, 0, 0, NULL, "memory allocation error");
    return STATUS_ERROR;
  }

  wcscpy (StringIdentifier->StringName, StringName);
  if (*NewId != STRING_ID_INVALID) {
    StringIdentifier->Index = *NewId;
    StringIdentifier->Flags |= STRING_FLAGS_INDEX_ASSIGNED;
    if (mDBData.NumStringIdentifiers <= StringIdentifier->Index) {
      mDBData.NumStringIdentifiers = StringIdentifier->Index + 1;
    }
  } else {
    StringIdentifier->Index = mDBData.NumStringIdentifiers++;
  }

  StringIdentifier->Flags |= Flags;
  //
  // Add it to our list of string identifiers
  //
  if (mDBData.StringIdentifier == NULL) {
    mDBData.StringIdentifier = StringIdentifier;
  } else {
    mDBData.LastStringIdentifier->Next = StringIdentifier;
  }

  mDBData.LastStringIdentifier    = StringIdentifier;
  mDBData.CurrentStringIdentifier = StringIdentifier;
  *NewId                          = (UINT16) StringIdentifier->Index;
  return Status;
}

/*****************************************************************************/

/*++

Routine Description:

  Add a new string to the database.

Arguments:

  LanguageName    - "eng" or "spa" language name
  StringName      - "STR_MY_TEXT" string name
  Scope           - from the #scope statements in the string file
  Format          - if we should format the string
  Flags           - characteristic flags for the string

Returns:

  STATUS

Notes:

  Several of the fields can be "inherited" from the previous calls to
  our database functions. For example, if scope is NULL here, then
  we'll use the previous setting.

--*/
STATUS
StringDBAddString (
  WCHAR   *LanguageName,
  WCHAR   *StringName,
  WCHAR   *Scope,
  WCHAR   *String,
  BOOLEAN Format,
  UINT16  Flags
  )
{
  LANGUAGE_LIST     *Lang;
  UINT32            Size;
  STRING_LIST       *Str;
  UINT16            StringIndex;
  WCHAR             TempLangName[4];
  STRING_IDENTIFIER *StringIdentifier;

  //
  // Check that language name is exactly 3 characters, or emit an error.
  // Truncate at 3 if it's longer, or make it 3 if it's shorter.
  //
  if (LanguageName != NULL) {
    Size = wcslen (LanguageName);
    if (Size != 3) {
      ParserError (0, "invalid length for language name", "%S", LanguageName);
      if (Size > 3) {
        LanguageName[3] = 0;
      } else {
        //
        // Make a local copy of the language name string, and extend to
        // 3 characters since we make assumptions elsewhere in this program
        // on the length.
        //
        wcscpy (TempLangName, LanguageName);
        for (; Size < 3; Size++) {
          TempLangName[Size] = L'?';
        }

        TempLangName[3] = 0;
        LanguageName    = TempLangName;
      }
    }
  }
  //
  // If they specified a language, make sure they've defined it already
  // via a #langdef statement. Otherwise use the current default language.
  //
  if (LanguageName != NULL) {
    Lang = StringDBFindLanguageList (LanguageName);
    if (Lang == NULL) {
      ParserError (0, "language not defined", "%S", LanguageName);
      return STATUS_ERROR;
    } else {
      StringDBSetCurrentLanguage (LanguageName);
    }
  } else {
    Lang = mDBData.CurrentLanguage;
    if (Lang == NULL) {
      //
      // Have to call SetLanguage() first
      //
      ParserError (0, "no language defined", "%S", StringName);
      return STATUS_ERROR;
    }
  }
  //
  // If they didn't define a string identifier, use the last string identifier
  // added.
  //
  if (StringName == NULL) {
    StringName = mDBData.CurrentStringIdentifier->StringName;
    if (StringName == NULL) {
      ParserError (0, "no string identifier previously specified", NULL);
      return STATUS_ERROR;
    }
  }
  //
  // If scope was not specified, use the default setting
  //
  if (Scope != NULL) {
    Scope = DuplicateString (Scope);
  } else {
    Scope = DuplicateString (mDBData.CurrentScope);
  }
  //
  // printf ("Adding string: %S.%S.%S\n", Lang->LanguageName, StringName, Scope);
  //
  // Check for duplicates for this Language.StringName.Scope. Allow multiple
  // definitions of the language name and printable language name, since the
  // user does not specifically define them.
  //
  if (StringDBFindString (Lang->LanguageName, StringName, Scope, NULL, NULL) != NULL) {
    if ((wcscmp (StringName, LANGUAGE_NAME_STRING_NAME) == 0) &&
        (wcscmp (StringName, PRINTABLE_LANGUAGE_NAME_STRING_NAME) == 0)
        ) {
      ParserError (
        0,
        "string multiply defined",
        "Language.Name.Scope = %S.%S.%S",
        Lang->LanguageName,
        StringName,
        Scope
        );
      return STATUS_ERROR;
    }
  }

  StringIndex = STRING_ID_INVALID;
  if (StringDBAddStringIdentifier (StringName, &StringIndex, Flags) != STATUS_SUCCESS) {
    return STATUS_ERROR;
  }

  StringIdentifier = StringDBFindStringIdentifierByName (StringName);
  //
  // Add this string to the end of the strings for this language.
  //
  Str = (STRING_LIST *) malloc (sizeof (STRING_LIST));
  if (Str == NULL) {
    Error (NULL, 0, 0, NULL, "memory allocation error");
    return STATUS_ERROR;
  }

  memset ((char *) Str, 0, sizeof (STRING_LIST));
  Size              = (wcslen (String) + 1) * sizeof (WCHAR);
  Str->Flags        = Flags;
  Str->Scope        = Scope;
  Str->StringName   = StringIdentifier->StringName;
  Str->LanguageName = DuplicateString (LanguageName);
  Str->Str          = (WCHAR *) MALLOC (Size);
  if (Str->Str == NULL) {
    Error (NULL, 0, 0, NULL, "memory allocation error");
    return STATUS_ERROR;
  }
  //
  // If not formatting, just copy the string.
  //
  wcscpy (Str->Str, String);
  if (Format) {
    StringDBFormatString (Str->Str);
  }
  //
  // Size may change after formatting. We set the size to
  // the actual size of the string, including the null for
  // easier processing later.
  //
  Str->Size = (wcslen (Str->Str) + 1) * sizeof (WCHAR);
  if (Lang->String == NULL) {
    Lang->String = Str;
  } else {
    Lang->LastString->Next = Str;
  }

  Lang->LastString = Str;
  return STATUS_SUCCESS;
}

/*****************************************************************************/

/*++

Routine Description:

  Given a language name, see if a language list for it has been defined

Arguments:

  LanguageName    - like "eng"

Returns:

  A pointer to the language list

--*/
static
LANGUAGE_LIST *
StringDBFindLanguageList (
  WCHAR *LanguageName
  )
{
  LANGUAGE_LIST *Lang;

  Lang = mDBData.LanguageList;
  while (Lang != NULL) {
    if (wcscmp (LanguageName, Lang->LanguageName) == 0) {
      break;
    }

    Lang = Lang->Next;
  }

  return Lang;
}

/*****************************************************************************/
STATUS
StringDBSetCurrentLanguage (
  WCHAR *LanguageName
  )
{
  LANGUAGE_LIST *Lang;

  Lang = StringDBFindLanguageList (LanguageName);
  if (Lang == NULL) {
    ParserError (0, "language not previously defined", "%S", LanguageName);
    return STATUS_ERROR;
  }

  mDBData.CurrentLanguage = Lang;
  return STATUS_SUCCESS;
}

/*****************************************************************************/
STATUS
StringDBAddLanguage (
  WCHAR *LanguageName,
  WCHAR *PrintableLanguageName
  )
{
  LANGUAGE_LIST *Lang;
  //
  // Check for redefinitions
  //
  Lang = StringDBFindLanguageList (LanguageName);
  if (Lang != NULL) {
    //
    // Better be the same printable name
    //
    if (wcscmp (PrintableLanguageName, Lang->PrintableLanguageName) != 0) {
      ParserError (
        0,
        "language redefinition",
        "%S:%S != %S:%S",
        Lang->LanguageName,
        Lang->PrintableLanguageName,
        LanguageName,
        PrintableLanguageName
        );
      return STATUS_ERROR;
      //
      //    } else {
      //      ParserWarning (0, "benign language redefinition", "%S", PrintableLanguageName);
      //      return STATUS_WARNING;
      //
    }
  } else {
    //
    // Allocate memory to keep track of this new language
    //
    Lang = (LANGUAGE_LIST *) malloc (sizeof (LANGUAGE_LIST));
    if (Lang == NULL) {
      Error (NULL, 0, 0, NULL, "memory allocation error");
      return STATUS_ERROR;
    }

    memset ((char *) Lang, 0, sizeof (LANGUAGE_LIST));
    //
    // Save the language name, then allocate memory to save the
    // printable language name
    //
    Lang->LanguageName[3] = 0;
    wcsncpy (Lang->LanguageName, LanguageName, 3);
    Lang->PrintableLanguageName = (WCHAR *) malloc ((wcslen (PrintableLanguageName) + 1) * sizeof (WCHAR));
    if (Lang->PrintableLanguageName == NULL) {
      Error (NULL, 0, 0, NULL, "memory allocation error");
      return STATUS_ERROR;
    }

    wcscpy (Lang->PrintableLanguageName, PrintableLanguageName);

    if (mDBData.LanguageList == NULL) {
      mDBData.LanguageList = Lang;
    } else {
      mDBData.LastLanguageList->Next = Lang;
    }

    mDBData.LastLanguageList = Lang;
  }
  //
  // Default is to make our active language this new one
  //
  StringDBSetCurrentLanguage (LanguageName);
  //
  // The first two strings for any language are the language name,
  // followed by the printable language name. Add them and set them
  // to referenced so they never get stripped out.
  //
  StringDBAddString (
    LanguageName,
    LANGUAGE_NAME_STRING_NAME,
    NULL,
    LanguageName,
    FALSE,
    STRING_FLAGS_REFERENCED
    );
  StringDBAddString (
    LanguageName,
    PRINTABLE_LANGUAGE_NAME_STRING_NAME,
    NULL,
    PrintableLanguageName,
    FALSE,
    STRING_FLAGS_REFERENCED
    );
  return STATUS_SUCCESS;
}

/*****************************************************************************/
static
STRING_IDENTIFIER *
StringDBFindStringIdentifierByName (
  WCHAR *StringName
  )
{
  STRING_IDENTIFIER *Identifier;

  Identifier = mDBData.StringIdentifier;
  while (Identifier != NULL) {
    if (wcscmp (StringName, Identifier->StringName) == 0) {
      return Identifier;
    }

    Identifier = Identifier->Next;
  }

  return NULL;
}

static
STRING_IDENTIFIER *
StringDBFindStringIdentifierByIndex (
  UINT32    StringIndex
  )
{
  STRING_IDENTIFIER *Identifier;

  Identifier = mDBData.StringIdentifier;
  while (Identifier != NULL) {
    if (Identifier->Index == StringIndex) {
      return Identifier;
    }

    Identifier = Identifier->Next;
  }

  return NULL;
}

/*****************************************************************************/
static
void
StringDBWriteStandardFileHeader (
  FILE *OutFptr
  )
{
  UINT32  TempIndex;
  for (TempIndex = 0; mSourceFileHeader[TempIndex] != NULL; TempIndex++) {
    fprintf (OutFptr, "%s\n", mSourceFileHeader[TempIndex]);
  }
}

/*****************************************************************************/

/*++

Routine Description:
  
  Given a Unicode string from an input file, reformat the string to replace
  backslash control sequences with the appropriate encoding.

Arguments:

  String        - pointer to string to reformat

Returns:

  Nothing

--*/
void
StringDBFormatString (
  WCHAR   *String
  )
{
  WCHAR *From;
  WCHAR *To;
  int   HexNibbles;
  WCHAR HexValue;
  //
  // Go through the string and process any formatting characters
  //
  From  = String;
  To    = String;
  while (*From) {
    if (*From == UNICODE_BACKSLASH) {
      //
      // First look for \wide and replace with the appropriate control character. Note that
      // when you have "define STR L"ABC"", then sizeof(ABC) is 8 because the null char is
      // counted. Make adjustments for this. We advance From below, so subtract 2 each time.
      //
      if (wcsncmp (From, UNICODE_WIDE_STRING, sizeof (UNICODE_WIDE_STRING) / sizeof (WCHAR) - 1) == 0) {
        *To = WIDE_CHAR;
        From += sizeof (UNICODE_WIDE_STRING) / sizeof (WCHAR) - 2;
      } else if (wcsncmp (From, UNICODE_NARROW_STRING, sizeof (UNICODE_NARROW_STRING) / sizeof (WCHAR) - 1) == 0) {
        //
        // Found: \narrow
        //
        *To = NARROW_CHAR;
        From += sizeof (UNICODE_NARROW_STRING) / sizeof (WCHAR) - 2;
      } else if (wcsncmp (From, UNICODE_NBR_STRING, sizeof (UNICODE_NBR_STRING) / sizeof (WCHAR) - 1) == 0) {
        //
        // Found: \nbr
        //
        *To = NON_BREAKING_CHAR;
        From += sizeof (UNICODE_NBR_STRING) / sizeof (WCHAR) - 2;
      } else if (wcsncmp (From, UNICODE_BR_STRING, sizeof (UNICODE_BR_STRING) / sizeof (WCHAR) - 1) == 0) {
        //
        // Found: \br -- pass through untouched
        //
        *To = *From;
      } else {
        //
        // Standard one-character control sequences such as \n, \r, \\, or \x
        //
        From++;
        switch (*From) {
        case ASCII_TO_UNICODE ('n'):
          *To = UNICODE_CR;
          To++;
          *To = UNICODE_LF;
          break;

        //
        // carriage return
        //
        case ASCII_TO_UNICODE ('r'):
          *To = UNICODE_CR;
          break;

        //
        // backslash
        //
        case UNICODE_BACKSLASH:
          *To = UNICODE_BACKSLASH;
          break;

        //
        // Tab
        //
        case ASCII_TO_UNICODE ('t'):
          *To = UNICODE_TAB;
          break;

        //
        // embedded double-quote
        //
        case UNICODE_DOUBLE_QUOTE:
          *To = UNICODE_DOUBLE_QUOTE;
          break;

        //
        // Hex Unicode character \x1234. We'll process up to 4 hex characters
        //
        case ASCII_TO_UNICODE ('x'):
          HexValue = 0;
          for (HexNibbles = 0; HexNibbles < 4; HexNibbles++) {
            if ((From[1] >= UNICODE_0) && (From[1] <= UNICODE_9)) {
              HexValue = (HexValue << 4) | (From[1] - UNICODE_0);
            } else if ((From[1] >= UNICODE_a) && (From[1] <= UNICODE_f)) {
              HexValue = (HexValue << 4) | (10 + From[1] - UNICODE_a);
            } else if ((From[1] >= UNICODE_A) && (From[1] <= UNICODE_F)) {
              HexValue = (HexValue << 4) | (10 + From[1] - UNICODE_A);
            } else {
              break;
            }

            From++;
          }

          if (HexNibbles == 0) {
            ParserWarning (
              0,
              "expected at least one valid hex digit with \\x escaped character in string",
              "\\%C",
              *From
              );
          } else {
            *To = HexValue;
          }
          break;

        default:
          *To = UNICODE_SPACE;
          ParserWarning (0, "invalid escaped character in string", "\\%C", *From);
          break;
        }
      }
    } else {
      *To = *From;
    }

    From++;
    To++;
  }

  *To = 0;
}

/*****************************************************************************/
STATUS
StringDBReadDatabase (
  INT8    *DBFileName,
  BOOLEAN IgnoreIfNotExist,
  BOOLEAN Verbose
  )
{
  STRING_DB_HEADER    DbHeader;
  STATUS              Status;
  FILE                *DBFptr;
  DB_DATA_ITEM_HEADER DataItemHeader;

  Status  = STATUS_SUCCESS;
  DBFptr  = NULL;
  //
  //  if (Verbose) {
  //    fprintf (stdout, "Reading database file %s\n", DBFileName);
  //  }
  //
  // Try to open the input file
  //
  if ((DBFptr = fopen (DBFileName, "rb")) == NULL) {
    if (IgnoreIfNotExist) {
      return STATUS_SUCCESS;
    }

    Error (NULL, 0, 0, DBFileName, "failed to open input database file for reading");
    return STATUS_ERROR;
  }
  //
  // Read and verify the database header
  //
  if (fread ((void *) &DbHeader, sizeof (STRING_DB_HEADER), 1, DBFptr) != 1) {
    Error (NULL, 0, 0, DBFileName, "failed to read header from database file");
    Status = STATUS_ERROR;
    goto Finish;
  }

  if (DbHeader.Key != STRING_DB_KEY) {
    Error (NULL, 0, 0, DBFileName, "invalid header in database file");
    Status = STATUS_ERROR;
    goto Finish;
  }

  if ((DbHeader.Version & STRING_DB_MAJOR_VERSION_MASK) != (STRING_DB_VERSION & STRING_DB_MAJOR_VERSION_MASK)) {
    Error (NULL, 0, 0, DBFileName, "incompatible database file version -- rebuild clean");
    Status = STATUS_ERROR;
    goto Finish;
  }
  //
  // Read remaining items
  //
  while (fread (&DataItemHeader, sizeof (DataItemHeader), 1, DBFptr) == 1) {
    switch (DataItemHeader.DataType) {
    case DB_DATA_TYPE_STRING_IDENTIFIER:
      StringDBReadStringIdentifier (DBFptr);
      break;

    case DB_DATA_TYPE_LANGUAGE_DEFINITION:
      StringDBReadLanguageDefinition (DBFptr);
      break;

    case DB_DATA_TYPE_STRING_DEFINITION:
      StringDBReadString (DBFptr);
      break;

    default:
      Error (
        NULL,
        0,
        0,
        "database corrupted",
        "invalid data item type 0x%X at offset 0x%X",
        (UINT32) DataItemHeader.DataType,
        ftell (DBFptr) - sizeof (DataItemHeader)
        );
      Status = STATUS_ERROR;
      goto Finish;
    }
  }

Finish:
  if (DBFptr != NULL) {
    fclose (DBFptr);
  }

  return Status;
}

/*****************************************************************************/

/*++

Routine Description:
  
  Write everything we know to the output database file. Write:

  Database header
  String identifiers[]
  StringPacks[]

Arguments:

  DBFileName    - name of the file to write to
  Verbose       - for debug purposes, print info messages along the way.

Returns:

  STATUS

--*/
STATUS
StringDBWriteDatabase (
  INT8    *DBFileName,
  BOOLEAN Verbose
  )
{
  STRING_DB_HEADER  DbHeader;
  UINT32            Counter;
  UINT32            StrLen;
  LANGUAGE_LIST     *Lang;
  STRING_IDENTIFIER *StringIdentifier;
  STRING_LIST       *StrList;
  FILE              *DBFptr;

  if (Verbose) {
    fprintf (stdout, "Writing database %s\n", DBFileName);
  }

  if ((DBFptr = fopen (DBFileName, "wb")) == NULL) {
    Error (NULL, 0, 0, DBFileName, "failed to open output database file for writing");
    return STATUS_ERROR;
  }
  //
  // Fill in and write the database header
  //
  memset (&DbHeader, 0, sizeof (STRING_DB_HEADER));
  DbHeader.HeaderSize = sizeof (STRING_DB_HEADER);
  DbHeader.Key        = STRING_DB_KEY;
  DbHeader.Version    = STRING_DB_VERSION;
  //
  // Count the number of languages we have
  //
  for (Lang = mDBData.LanguageList; Lang != NULL; Lang = Lang->Next) {
    DbHeader.NumLanguages++;
  }
  //
  // Count up how many string identifiers we have, and total up the
  // size of the names plus the size of the flags field we will
  // write out too.
  //
  DbHeader.NumStringIdenfiers = mDBData.NumStringIdentifiers;
  StringIdentifier            = mDBData.StringIdentifier;
  for (Counter = 0; Counter < mDBData.NumStringIdentifiers; Counter++) {
    StrLen = wcslen (StringIdentifier->StringName) + 1;
    DbHeader.StringIdentifiersSize += StrLen * sizeof (WCHAR) + sizeof (StringIdentifier->Flags);
    StringIdentifier = StringIdentifier->Next;
  }

  //
  // Write the header
  //
  fwrite (&DbHeader, sizeof (STRING_DB_HEADER), 1, DBFptr);
  if (Verbose) {
    fprintf (stdout, "  Number of string identifiers  0x%04X\n", DbHeader.NumStringIdenfiers);
    fprintf (stdout, "  Number of languages           %d\n", DbHeader.NumLanguages);
  }
  //
  // Write the string identifiers
  //
  for (StringIdentifier = mDBData.StringIdentifier; StringIdentifier != NULL; StringIdentifier = StringIdentifier->Next) {
    StringDBWriteStringIdentifier (
      DBFptr,
      (UINT16) StringIdentifier->Index,
      StringIdentifier->Flags,
      StringIdentifier->StringName
      );
  }
  //
  // Now write all the strings for each language
  //
  for (Lang = mDBData.LanguageList; Lang != NULL; Lang = Lang->Next) {
    StringDBWriteLanguageDefinition (DBFptr, Lang->LanguageName, Lang->PrintableLanguageName);
    for (StrList = Lang->String; StrList != NULL; StrList = StrList->Next) {
      StringDBWriteString (
        DBFptr,
        StrList->Flags,
        Lang->LanguageName,
        StrList->StringName,
        StrList->Scope,
        StrList->Str
        );
    }
  }

  fclose (DBFptr);
  return STATUS_SUCCESS;
}

STATUS
StringDBSetStringReferenced (
  INT8      *StringIdentifierName,
  BOOLEAN   IgnoreNotFound
  )
{
  STRING_IDENTIFIER *Id;
  WCHAR             *WName;
  STATUS            Status;
  //
  // See if it's already been defined.
  //
  Status  = STATUS_SUCCESS;
  WName   = (WCHAR *) malloc ((strlen (StringIdentifierName) + 1) * sizeof (WCHAR));
#ifdef USE_VC8
  swprintf (WName, (strlen (StringIdentifierName) + 1) * sizeof (WCHAR), L"%S", StringIdentifierName);
#else
  swprintf (WName, L"%S", StringIdentifierName);
#endif
  Id = StringDBFindStringIdentifierByName (WName);
  if (Id != NULL) {
    Id->Flags |= STRING_FLAGS_REFERENCED;
  } else {
    if (IgnoreNotFound == 0) {
      ParserWarning (0, StringIdentifierName, "string identifier not found in database");
      Status = STATUS_WARNING;
    }
  }

  free (WName);
  return Status;
}

/*****************************************************************************/

/*++

Routine Description:

  Dump the contents of a database to an output unicode file.

Arguments:

  DBFileName        - name of the pre-existing database file to read
  OutputFileName    - name of the file to dump the database contents to
  Verbose           - for printing of additional info useful for debugging

Returns:

  STATUS

Notes:

  There's some issue with the unicode printing routines. Therefore to 
  write to the output file properly, open it as binary and use fwrite.
  Ideally we could open it with just L"w" and use fwprintf().

--*/
STATUS
StringDBDumpDatabase (
  INT8                *DBFileName,
  INT8                *OutputFileName,
  BOOLEAN             Verbose
  )
{
  LANGUAGE_LIST     *Lang;
  STRING_IDENTIFIER *StringIdentifier;
  STRING_LIST       *StrList;
  FILE              *OutFptr;
  WCHAR             WChar;
  WCHAR             *WOutputFileName;
  WCHAR             CrLf[2];
  WCHAR             Line[200];
  WCHAR             *Scope;
  //
  // This function assumes the database has already been read, and
  // we're just dumping our internal data structures to a unicode file.
  //
  if (Verbose) {
    fprintf (stdout, "Dumping database file %s\n", DBFileName);
  }

  WOutputFileName = AsciiToWchar (OutputFileName);
  OutFptr         = _wfopen (WOutputFileName, L"wb");
  free (WOutputFileName);
  if (OutFptr == NULL) {
    Error (NULL, 0, 0, OutputFileName, "failed to open output file for writing");
    return STATUS_ERROR;
  }

  WChar = UNICODE_FILE_START;
  fwrite (&WChar, sizeof (WCHAR), 1, OutFptr);
  CrLf[1] = UNICODE_LF;
  CrLf[0] = UNICODE_CR;
  //
  // The default control character is '/'. Make it '#' by writing
  // "/=#" to the output file.
  //
#ifdef USE_VC8
  swprintf (Line, wcslen(Line) * sizeof (WCHAR), L"/=#");
#else
  swprintf (Line, L"/=#");
#endif
  fwrite (Line, wcslen (Line) * sizeof (WCHAR), 1, OutFptr);
  fwrite (&CrLf, sizeof (CrLf), 1, OutFptr);
  fwrite (&CrLf, sizeof (CrLf), 1, OutFptr);
  //
  // Dump all the string identifiers and their values
  //
  StringDBAssignStringIndexes ();
  for (StringIdentifier = mDBData.StringIdentifier; StringIdentifier != NULL; StringIdentifier = StringIdentifier->Next) {
    //
    // Write the "#define " string
    //
    if (StringIdentifier->Flags & STRING_FLAGS_REFERENCED) {
#ifdef USE_VC8
      swprintf (
        Line,
        wcslen(Line) * sizeof (WCHAR),
        L"%s %-60.60s 0x%04X",
        DEFINE_STR,
        StringIdentifier->StringName,
        StringIdentifier->Index
        );
#else
      swprintf (
        Line,
        L"%s %-60.60s 0x%04X",
        DEFINE_STR,
        StringIdentifier->StringName,
        StringIdentifier->Index
        );
#endif
    } else {
#ifdef USE_VC8
      swprintf (
        Line,
        wcslen(Line) * sizeof (WCHAR), 
        L"%s %-60.60s 0x%04X  // NOT REFERENCED",
        DEFINE_STR,
        StringIdentifier->StringName,
        StringIdentifier->Index
        );
#else
      swprintf (
        Line,
        L"%s %-60.60s 0x%04X  // NOT REFERENCED",
        DEFINE_STR,
        StringIdentifier->StringName,
        StringIdentifier->Index
        );
#endif
    }

    fwrite (Line, wcslen (Line) * sizeof (WCHAR), 1, OutFptr);
    fwrite (&CrLf, sizeof (CrLf), 1, OutFptr);
  }

  fwrite (&CrLf, sizeof (CrLf), 1, OutFptr);
  //
  // Now write all the strings for each language.
  //
  WChar = UNICODE_DOUBLE_QUOTE;
  Scope = NULL;
  for (Lang = mDBData.LanguageList; Lang != NULL; Lang = Lang->Next) {
    fwrite (&CrLf, sizeof (CrLf), 1, OutFptr);
#ifdef USE_VC8
    swprintf (Line, wcslen(Line) * sizeof (WCHAR), L"#langdef %s \"%s\"", Lang->LanguageName, Lang->PrintableLanguageName);
#else
    swprintf (Line, L"#langdef %s \"%s\"", Lang->LanguageName, Lang->PrintableLanguageName);
#endif
    fwrite (Line, wcslen (Line) * sizeof (WCHAR), 1, OutFptr);
    fwrite (&CrLf, sizeof (CrLf), 1, OutFptr);
    fwrite (&CrLf, sizeof (CrLf), 1, OutFptr);
    //
    // Now the strings (in double-quotes) for this language. Write
    // #string STR_NAME  #language eng "string"
    //
    for (StrList = Lang->String; StrList != NULL; StrList = StrList->Next) {
      //
      // Print the internal flags for debug
      //
#ifdef USE_VC8
      swprintf (Line, wcslen(Line) * sizeof (WCHAR), L"// flags=0x%02X", (UINT32) StrList->Flags);
#else
      swprintf (Line, L"// flags=0x%02X", (UINT32) StrList->Flags);
#endif
      fwrite (Line, wcslen (Line) * sizeof (WCHAR), 1, OutFptr);
      fwrite (&CrLf, sizeof (CrLf), 1, OutFptr);
      //
      // Print the scope if changed
      //
      if ((Scope == NULL) || (wcscmp (Scope, StrList->Scope) != 0)) {
#ifdef USE_VC8
        swprintf (Line, wcslen(Line) * sizeof (WCHAR), L"#scope %s", StrList->Scope);
#else
        swprintf (Line, L"#scope %s", StrList->Scope);
#endif
        fwrite (Line, wcslen (Line) * sizeof (WCHAR), 1, OutFptr);
        fwrite (&CrLf, sizeof (CrLf), 1, OutFptr);
        Scope = StrList->Scope;
      }

#ifdef USE_VC8
      swprintf (
        Line,
        wcslen(Line) * sizeof (WCHAR), 
        L"#string %-50.50s #language %s \"",
        StrList->StringName,
        Lang->LanguageName
        );
#else
      swprintf (
        Line,
        L"#string %-50.50s #language %s \"",
        StrList->StringName,
        Lang->LanguageName
        );
#endif
      fwrite (Line, wcslen (Line) * sizeof (WCHAR), 1, OutFptr);
      fwrite (StrList->Str, StrList->Size - sizeof (WCHAR), 1, OutFptr);
#ifdef USE_VC8
      swprintf (Line, wcslen(Line) * sizeof (WCHAR), L"\"");
#else
      swprintf (Line, L"\"");
#endif
      fwrite (Line, wcslen (Line) * sizeof (WCHAR), 1, OutFptr);
      fwrite (&CrLf, sizeof (CrLf), 1, OutFptr);
    }
  }

  fclose (OutFptr);
  return STATUS_SUCCESS;
}

/*****************************************************************************/

/*++

Routine Description:

  Given a primary language, a string identifier number, and a list of
  languages, find a secondary string.

Arguments:

  LanguageName      - primary language, like "spa"
  StringId          - string index value
  LanguageList      - linked list of "eng", "spa+cat",...

Returns:

  Pointer to a secondary string if found. NULL otherwise.

Notes:
 
  Given: LanguageName "spa"   and  LanguageList "spa+cat", match the
  "spa" and extract the "cat" and see if there is a string defined
  for "cat".StringId.

--*/
static
STATUS
StringDBWriteStringIdentifier (
  FILE                *DBFptr,
  UINT16              StringId,
  UINT16              Flags,
  WCHAR               *IdentifierName
  )
{
  DB_DATA_ITEM_HEADER Hdr;
  memset (&Hdr, 0, sizeof (DB_DATA_ITEM_HEADER));
  Hdr.DataType = DB_DATA_TYPE_STRING_IDENTIFIER;
  if (fwrite (&Hdr, sizeof (DB_DATA_ITEM_HEADER), 1, DBFptr) != 1) {
    Error (NULL, 0, 0, "failed to write string to output database file", NULL);
    return STATUS_ERROR;
  }

  if (fwrite (&StringId, sizeof (StringId), 1, DBFptr) != 1) {
    Error (NULL, 0, 0, "failed to write StringId to output database", NULL);
    return STATUS_ERROR;
  }

  if (fwrite (&Flags, sizeof (Flags), 1, DBFptr) != 1) {
    Error (NULL, 0, 0, "failed to write StringId flags to output database", NULL);
    return STATUS_ERROR;
  }

  if (StringDBWriteGenericString (DBFptr, IdentifierName) != STATUS_SUCCESS) {
    return STATUS_ERROR;
  }

  return STATUS_SUCCESS;
}

static
STATUS
StringDBReadStringIdentifier (
  FILE                *DBFptr
  )
{
  WCHAR   *IdentifierName;
  UINT16  Flags;
  UINT16  StringId;
  UINT16  Size;

  if (fread (&StringId, sizeof (StringId), 1, DBFptr) != 1) {
    Error (NULL, 0, 0, "failed to read StringId from database", NULL);
    return STATUS_ERROR;
  }

  if (fread (&Flags, sizeof (Flags), 1, DBFptr) != 1) {
    Error (NULL, 0, 0, "failed to read StringId flags from database", NULL);
    return STATUS_ERROR;
  }

  if (StringDBReadGenericString (DBFptr, &Size, &IdentifierName) != STATUS_SUCCESS) {
    return STATUS_ERROR;
  }

  StringDBAddStringIdentifier (IdentifierName, &StringId, Flags);
  //
  // printf ("STRID:  0x%04X %S\n", (UINT32)StringId, IdentifierName);
  //
  FREE (IdentifierName);
  return STATUS_SUCCESS;
}

static
STATUS
StringDBWriteString (
  FILE            *DBFptr,
  UINT16          Flags,
  WCHAR           *Language,
  WCHAR           *StringName,
  WCHAR           *Scope,
  WCHAR           *Str
  )
{
  DB_DATA_ITEM_HEADER Hdr;
  memset (&Hdr, 0, sizeof (DB_DATA_ITEM_HEADER));
  Hdr.DataType = DB_DATA_TYPE_STRING_DEFINITION;
  if (fwrite (&Hdr, sizeof (DB_DATA_ITEM_HEADER), 1, DBFptr) != 1) {
    Error (NULL, 0, 0, "failed to write string header to output database file", NULL);
    return STATUS_ERROR;
  }

  if (fwrite (&Flags, sizeof (Flags), 1, DBFptr) != 1) {
    Error (NULL, 0, 0, "failed to write string flags to output database", NULL);
    return STATUS_ERROR;
  }

  if (StringDBWriteGenericString (DBFptr, Language) != STATUS_SUCCESS) {
    return STATUS_ERROR;
  }

  if (StringDBWriteGenericString (DBFptr, StringName) != STATUS_SUCCESS) {
    return STATUS_ERROR;
  }

  if (StringDBWriteGenericString (DBFptr, Scope) != STATUS_SUCCESS) {
    return STATUS_ERROR;
  }

  if (StringDBWriteGenericString (DBFptr, Str) != STATUS_SUCCESS) {
    return STATUS_ERROR;
  }
  //
  // printf ("DBWriteString: %S.%S.%S\n", Language, StringName, Scope);
  //
  return STATUS_SUCCESS;
}

static
STATUS
StringDBReadString (
  FILE            *DBFptr
  )
{
  UINT16  Flags;
  UINT16  Size;
  WCHAR   *Language;
  WCHAR   *StringName;
  WCHAR   *Scope;
  WCHAR   *Str;

  if (fread (&Flags, sizeof (Flags), 1, DBFptr) != 1) {
    Error (NULL, 0, 0, "failed to read string flags from database", NULL);
    return STATUS_ERROR;
  }

  if (StringDBReadGenericString (DBFptr, &Size, &Language) != STATUS_SUCCESS) {
    return STATUS_ERROR;
  }

  if (StringDBReadGenericString (DBFptr, &Size, &StringName) != STATUS_SUCCESS) {
    return STATUS_ERROR;
  }

  if (StringDBReadGenericString (DBFptr, &Size, &Scope) != STATUS_SUCCESS) {
    return STATUS_ERROR;
  }

  if (StringDBReadGenericString (DBFptr, &Size, &Str) != STATUS_SUCCESS) {
    return STATUS_ERROR;
  }
  //
  // If the first or second string (language name and printable language name),
  // then skip them. They're added via language definitions data items in
  // the database.
  //
  if (StringName[0] != L'$') {
    StringDBAddString (Language, StringName, Scope, Str, FALSE, Flags);
  }
  //
  // printf ("DBReadString: %S.%S.%S\n", Language, StringName, Scope);
  //
  FREE (Language);
  FREE (StringName);
  if (Str != NULL) {
    FREE (Str);
  }

  if (Scope != NULL) {
    FREE (Scope);
  }

  return STATUS_SUCCESS;
}

static
STATUS
StringDBWriteLanguageDefinition (
  FILE            *DBFptr,
  WCHAR           *LanguageName,
  WCHAR           *PrintableLanguageName
  )
{
  DB_DATA_ITEM_HEADER Hdr;
  memset (&Hdr, 0, sizeof (DB_DATA_ITEM_HEADER));
  Hdr.DataType = DB_DATA_TYPE_LANGUAGE_DEFINITION;
  if (fwrite (&Hdr, sizeof (DB_DATA_ITEM_HEADER), 1, DBFptr) != 1) {
    Error (NULL, 0, 0, "failed to write string to output database file", NULL);
    return STATUS_ERROR;
  }

  if (StringDBWriteGenericString (DBFptr, LanguageName) != STATUS_SUCCESS) {
    return STATUS_ERROR;
  }

  if (StringDBWriteGenericString (DBFptr, PrintableLanguageName) != STATUS_SUCCESS) {
    return STATUS_ERROR;
  }

  return STATUS_SUCCESS;
}

static
STATUS
StringDBReadLanguageDefinition (
  FILE            *DBFptr
  )
{
  WCHAR   *LanguageName;
  WCHAR   *PrintableLanguageName;
  UINT16  Size;
  STATUS  Status;

  if (StringDBReadGenericString (DBFptr, &Size, &LanguageName) != STATUS_SUCCESS) {
    return STATUS_ERROR;
  }

  if (StringDBReadGenericString (DBFptr, &Size, &PrintableLanguageName) != STATUS_SUCCESS) {
    return STATUS_ERROR;
  }
  //
  // printf("LANG: %S %S\n", LanguageName, PrintableLanguageName);
  //
  Status = StringDBAddLanguage (LanguageName, PrintableLanguageName);
  FREE (LanguageName);
  FREE (PrintableLanguageName);
  return Status;
}
//
// All unicode strings in the database consist of a UINT16 length
// field, followed by the string itself. This routine reads one
// of those and returns the info.
//
static
STATUS
StringDBReadGenericString (
  FILE      *DBFptr,
  UINT16    *Size,
  WCHAR     **Str
  )
{
  UINT16  LSize;
  UINT16  Flags;
  WCHAR   *LStr;

  if (fread (&LSize, sizeof (UINT16), 1, DBFptr) != 1) {
    Error (NULL, 0, 0, "failed to read a string length field from the database", NULL);
    return STATUS_ERROR;
  }

  if (fread (&Flags, sizeof (UINT16), 1, DBFptr) != 1) {
    Error (NULL, 0, 0, "failed to read a string flags field from the database", NULL);
    return STATUS_ERROR;
  }

  LStr = MALLOC (LSize);
  if (LStr == NULL) {
    Error (__FILE__, __LINE__, 0, "memory allocation failed reading the database", NULL);
    return STATUS_ERROR;
  }

  if (fread (LStr, sizeof (WCHAR), (UINT32) LSize / sizeof (WCHAR), DBFptr) != (UINT32) LSize / sizeof (WCHAR)) {
    Error (NULL, 0, 0, "failed to read string from database", NULL);
    Error (NULL, 0, 0, "database read failure", "offset 0x%X", ftell (DBFptr));
    free (LStr);
    return STATUS_ERROR;
  }
  //
  // printf ("DBR: %S\n", LStr);
  //
  // If the flags field indicated we were asked to write a NULL string, then
  // return them a NULL pointer.
  //
  if (Flags & STRING_FLAGS_UNDEFINED) {
    *Size = 0;
    *Str  = NULL;
  } else {
    *Size = LSize;
    *Str  = LStr;
  }

  return STATUS_SUCCESS;
}

static
STATUS
StringDBWriteGenericString (
  FILE      *DBFptr,
  WCHAR     *Str
  )
{
  UINT16  Size;
  UINT16  Flags;
  WCHAR   ZeroString[1];
  //
  // Strings in the database consist of a size UINT16 followed
  // by the string itself.
  //
  if (Str == NULL) {
    ZeroString[0] = 0;
    Str           = ZeroString;
    Size          = sizeof (ZeroString);
    Flags         = STRING_FLAGS_UNDEFINED;
  } else {
    Flags = 0;
    Size  = (UINT16) ((wcslen (Str) + 1) * sizeof (WCHAR));
  }

  if (fwrite (&Size, sizeof (UINT16), 1, DBFptr) != 1) {
    Error (NULL, 0, 0, "failed to write string size to database", NULL);
    return STATUS_ERROR;
  }

  if (fwrite (&Flags, sizeof (UINT16), 1, DBFptr) != 1) {
    Error (NULL, 0, 0, "failed to write string flags to database", NULL);
    return STATUS_ERROR;
  }

  if (fwrite (Str, sizeof (WCHAR), Size / sizeof (WCHAR), DBFptr) != Size / sizeof (WCHAR)) {
    Error (NULL, 0, 0, "failed to write string to database", NULL);
    return STATUS_ERROR;
  }

  return STATUS_SUCCESS;
}

static
STRING_LIST *
StringDBFindString (
  WCHAR                       *LanguageName,
  WCHAR                       *StringName,
  WCHAR                       *Scope,
  WCHAR_STRING_LIST           *LanguagesOfInterest,
  WCHAR_MATCHING_STRING_LIST  *IndirectionList
  )
{
  LANGUAGE_LIST               *Lang;
  STRING_LIST                 *CurrString;
  WCHAR_MATCHING_STRING_LIST  *IndListPtr;
  WCHAR                       TempLangName[LANGUAGE_IDENTIFIER_NAME_LEN + 1];
  WCHAR                       *WCharPtr;

  //
  // If we were given an indirection list, then see if one was specified for this
  // string identifier. That is to say, if the indirection says "STR_ID_MY_FAVORITE MyScope",
  // then if this string name matches one in the list, then do a lookup with the
  // specified scope and return that value.
  //
  if (IndirectionList != NULL) {
    for (IndListPtr = IndirectionList; IndListPtr != NULL; IndListPtr = IndListPtr->Next) {
      if (wcscmp (StringName, IndListPtr->Str1) == 0) {
        CurrString = StringDBFindString (LanguageName, StringName, IndListPtr->Str2, LanguagesOfInterest, NULL);
        if (CurrString != NULL) {
          return CurrString;
        }
      }
    }
  }
  //
  // First look for exact match language.stringname
  //
  for (Lang = mDBData.LanguageList; Lang != NULL; Lang = Lang->Next) {
    if (wcscmp (LanguageName, Lang->LanguageName) == 0) {
      //
      // Found language match. Try to find string name match
      //
      for (CurrString = Lang->String; CurrString != NULL; CurrString = CurrString->Next) {
        if (wcscmp (StringName, CurrString->StringName) == 0) {
          //
          // Found a string name match. See if we're supposed to find
          // a scope match.
          //
          if (Scope != NULL) {
            if (wcscmp (CurrString->Scope, Scope) == 0) {
              return CurrString;
            }
          } else {
            return CurrString;
          }
        }
      }
    }
  }
  //
  // If we got here, then we didn't find a match. Look for secondary string
  // matches. That is to say, if we're processing "spa", and they requested
  // "spa+cat", then recursively call with "cat"
  //
  while (LanguagesOfInterest != NULL) {
    //
    // If this is the language we're looking for, then process the
    // languages of interest list for it.
    //
    if (wcsncmp (LanguageName, LanguagesOfInterest->Str, LANGUAGE_IDENTIFIER_NAME_LEN) == 0) {
      WCharPtr = LanguagesOfInterest->Str + LANGUAGE_IDENTIFIER_NAME_LEN;
      while (*WCharPtr) {
        //
        // Double-check the length, though it should have been checked on the
        // command line.
        //
        if (wcslen (WCharPtr) < LANGUAGE_IDENTIFIER_NAME_LEN) {
          Error (NULL, 0, 0, "malformed alternate language list", "%S", LanguagesOfInterest->Str);
          return NULL;
        }

        wcsncpy (TempLangName, WCharPtr, LANGUAGE_IDENTIFIER_NAME_LEN);
        TempLangName[LANGUAGE_IDENTIFIER_NAME_LEN]  = 0;
        CurrString = StringDBFindString (TempLangName, StringName, NULL, NULL, IndirectionList);
        if (CurrString != NULL) {
          return CurrString;
        }

        WCharPtr += LANGUAGE_IDENTIFIER_NAME_LEN;
      }
    }

    LanguagesOfInterest = LanguagesOfInterest->Next;
  }

  return NULL;
}

STATUS
StringDBSetScope (
  WCHAR   *Scope
  )
{
  //
  // Free up existing scope memory.
  //
  if (mDBData.CurrentScope != NULL) {
    FREE (mDBData.CurrentScope);
  }

  mDBData.CurrentScope = DuplicateString (Scope);
  return STATUS_SUCCESS;
}
//
// We typically don't assign index values to string identifiers
// until we're ready to write out files. To reduce the size of
// the output file, re-order the string identifiers to move any
// unreferenced ones to the end. Then we'll walk the list
// again to assign string indexes, keeping track of the last
// one referenced.
//
static
void
StringDBAssignStringIndexes (
  VOID
  )
{
  STRING_IDENTIFIER *StrId;
  STRING_IDENTIFIER *FirstUsed;
  STRING_IDENTIFIER *LastUsed;
  STRING_IDENTIFIER *FirstUnused;
  STRING_IDENTIFIER *LastUnused;
  UINT32            Index;
  UINT32            MaxReferenced;

  //
  // Create two lists -- used and unused. Then put them together with
  // the unused ones on the end.
  //
  FirstUsed   = NULL;
  LastUsed    = NULL;
  FirstUnused = NULL;
  LastUnused  = NULL;
  StrId       = mDBData.StringIdentifier;
  while (StrId != NULL) {
    if ((StrId->Flags & STRING_FLAGS_REFERENCED) == 0) {
      //
      // Put it on the unused list
      //
      if (FirstUnused == NULL) {
        FirstUnused = StrId;
      } else {
        LastUnused->Next = StrId;
      }

      LastUnused        = StrId;
      StrId             = StrId->Next;
      LastUnused->Next  = NULL;
    } else {
      //
      // Put it on the used list
      //
      if (FirstUsed == NULL) {
        FirstUsed = StrId;
      } else {
        LastUsed->Next = StrId;
      }

      LastUsed        = StrId;
      StrId           = StrId->Next;
      LastUsed->Next  = NULL;
    }
  }
  //
  // Join the lists
  //
  if (FirstUsed != NULL) {
    mDBData.StringIdentifier  = FirstUsed;
    LastUsed->Next            = FirstUnused;
  } else {
    mDBData.StringIdentifier = FirstUnused;
  }

  MaxReferenced = 0;
  Index         = 0;
  for (StrId = mDBData.StringIdentifier; StrId != NULL; StrId = StrId->Next) {
    StrId->Index = Index;
    Index++;
    if (StrId->Flags & STRING_FLAGS_REFERENCED) {
      mDBData.NumStringIdentifiersReferenced = Index;
    }
  }

  mDBData.NumStringIdentifiers = Index;
}

static
WCHAR *
DuplicateString (
  WCHAR   *Str
  )
{
  WCHAR *NewStr;
  if (Str == NULL) {
    return NULL;
  }

  NewStr = MALLOC ((wcslen (Str) + 1) * sizeof (WCHAR));
  if (NewStr == NULL) {
    Error (NULL, 0, 0, "memory allocation failure", NULL);
    return NULL;
  }

  wcscpy (NewStr, Str);
  return NewStr;
}

static
WCHAR *
AsciiToWchar (
  INT8 *Str
  )
{
  UINT32  Len;
  WCHAR   *NewStr;
  WCHAR   *Ptr;

  Len     = strlen (Str) + 1;
  NewStr  = (WCHAR *) malloc (Len * sizeof (WCHAR));
  for (Ptr = NewStr; *Str != 0; Str++, Ptr++) {
    *Ptr = (UINT16) (UINT8) *Str;
  }

  *Ptr = 0;
  return NewStr;
}

/*****************************************************************************/

/*++

Routine Description:

  Create an HII export string pack for the strings in our database.

Arguments:

  FileName        - name of the output file to write 

Returns:

  STATUS


--*/
STATUS
StringDBCreateHiiExportPack (
  INT8                        *FileName,
  WCHAR_STRING_LIST           *LanguagesOfInterest
  )
{
  FILE                        *Fptr;
  LANGUAGE_LIST               *Lang;
  STRING_LIST                 *CurrString;
  STRING_LIST                 EmptyString;
  UINT32                      Offset;
  UINT32                      StringIndex;
  UINT32                      TempIndex;
  EFI_HII_STRING_PACK_HEADER  StringPack;
  UINT32                      Len;
  WCHAR                       ZeroString[1];
  WCHAR                       *TempStringPtr;
  WCHAR                       *LangName;
  STRING_IDENTIFIER           *StringIdentifier;
  WCHAR_STRING_LIST           *LOIPtr;
  BOOLEAN                     LanguageOk;


  if ((Fptr = fopen (FileName, "wb")) == NULL) {
    Error (NULL, 0, 0, FileName, "failed to open output HII export file");
    return STATUS_ERROR;
  }
  //
  // Assign index values to the string identifiers
  //
  StringDBAssignStringIndexes ();
  //
  // If a given string is not defined, then we'll use this one.
  //
  memset (&EmptyString, 0, sizeof (EmptyString));
  EmptyString.Size  = sizeof (ZeroString);
  EmptyString.Str   = ZeroString;
  //
  // Process each language, then each string for each langage
  //
  ZeroString[0] = 0;
  for (Lang = mDBData.LanguageList; Lang != NULL; Lang = Lang->Next) {
    //
    // If we have a language list, then make sure this language is in that
    // list.
    //
    LanguageOk  = TRUE;
    if (LanguagesOfInterest != NULL) {
      LanguageOk = FALSE;
      for (LOIPtr = LanguagesOfInterest; LOIPtr != NULL; LOIPtr = LOIPtr->Next) {
        if (wcsncmp (LOIPtr->Str, Lang->LanguageName, LANGUAGE_IDENTIFIER_NAME_LEN) == 0) {
          LanguageOk  = TRUE;
          break;
        }
      }
    }

    if (!LanguageOk) {
      continue;
    }

    //
    // Process each string for this language. We have to make 3 passes on the strings:
    //   Pass1: computes sizes and fill in the string pack header
    //   Pass2: write the array of offsets
    //   Pass3: write the strings
    //
    //
    // PASS 1: Fill in and print the HII string pack header
    //
    // Compute the size for this language package and write
    // the header out. Each string package contains:
    //   Header
    //   Offset[]  -- an array of offsets to strings, of type RELOFST each
    //   String[]  -- the actual strings themselves
    //
    memset ((char *) &StringPack, 0, sizeof (EFI_HII_STRING_PACK_HEADER));
    StringPack.Header.Type        = EFI_HII_STRING;
    StringPack.NumStringPointers  = (UINT16) mDBData.NumStringIdentifiersReferenced;
    LangName                      = Lang->LanguageName;
    //
    // First string is the language name. If we're printing all languages, then
    // it's just the "spa". If we were given a list of languages to print, then it's
    // the "spacat" string. Compute its offset and fill in
    // the info in the header. Since we know the language name string's length,
    // and the printable language name follows it, use that info to fill in the
    // entry for the printable language name as well.
    //
    StringPack.LanguageNameString = (STRING_OFFSET) (sizeof (EFI_HII_STRING_PACK_HEADER) + (mDBData.NumStringIdentifiersReferenced * sizeof (STRING_OFFSET)));
    StringPack.PrintableLanguageName = (STRING_OFFSET) (StringPack.LanguageNameString + (wcslen (LangName) + 1) * sizeof (WCHAR));
    //
    // Add up the size of all strings so we can fill in our header.
    //
    Len = 0;
    for (StringIndex = 0; StringIndex < mDBData.NumStringIdentifiersReferenced; StringIndex++) {
      //
      // For the first string (language name), we print out the "spacat" if they
      // requested it. We set LangName to point to the proper language name string above.
      //
      if (StringIndex == STRING_ID_LANGUAGE_NAME) {
        Len += (wcslen (LangName) + 1) * sizeof (WCHAR);
      } else {
        //
        // Find a string with this language.stringname
        //
        StringIdentifier = StringDBFindStringIdentifierByIndex (StringIndex);
        if (StringIdentifier == NULL) {
          Error (NULL, 0, 0, "internal error", "invalid string index 0x%X", StringIndex);
          return STATUS_ERROR;
        }
        //
        // Find a matching string if this string identifier was referenced
        //
        EmptyString.Flags = STRING_FLAGS_UNDEFINED;
        CurrString        = NULL;
        if (StringIdentifier->Flags & STRING_FLAGS_REFERENCED) {
          CurrString = StringDBFindString (
                        Lang->LanguageName,
                        StringIdentifier->StringName,
                        NULL,
                        NULL, // LanguagesOfInterest,
                        NULL
                        );
          //
          // IndirectionList);
          //
          if (NULL == CurrString) {
            //
            // If string for Lang->LanguageName is not found, try to get an English version
            //
            CurrString = StringDBFindString (
                          L"eng",
                          StringIdentifier->StringName,
                          NULL,
                          NULL, // LanguagesOfInterest,
                          NULL
                          );
            //
            // IndirectionList);
            //
          }
        }

        if (CurrString == NULL) {
          CurrString = &EmptyString;
          EmptyString.Flags |= StringIdentifier->Flags;
        }

        Len += CurrString->Size;
      }
    }
    StringPack.Header.Length =    sizeof (EFI_HII_STRING_PACK_HEADER) 
                                + mDBData.NumStringIdentifiersReferenced * sizeof (STRING_OFFSET) 
                                + Len;
    //
    // Write out the string pack header
    //
    fwrite ((void *) &StringPack, sizeof (StringPack), 1, Fptr);
    //
    // PASS2 : write the offsets
    //
    // Traverse the list of strings again and write the array of offsets. The
    // offset to the first string is the size of the string pack header
    // plus the size of the offsets array. The other strings follow it.
    //
    StringIndex = 0;
    Offset      = sizeof (StringPack) + mDBData.NumStringIdentifiersReferenced * sizeof (STRING_OFFSET);
    for (StringIndex = 0; StringIndex < mDBData.NumStringIdentifiersReferenced; StringIndex++) {
      //
      // Write the offset
      //
      fwrite (&Offset, sizeof (STRING_OFFSET), 1, Fptr);
      //
      // Find the string name
      //
      StringIdentifier = StringDBFindStringIdentifierByIndex (StringIndex);
      if (StringIdentifier == NULL) {
        Error (NULL, 0, 0, "internal error", "invalid string index 0x%X", StringIndex);
        return STATUS_ERROR;
      }
      //
      // For the first string (language name), we print out the "spacat" if they
      // requested it. We set LangName to point to the proper language name string above.
      //
      if (StringIndex == STRING_ID_LANGUAGE_NAME) {
        Offset += (wcslen (LangName) + 1) * sizeof (WCHAR);
        CurrString = StringDBFindString (
                      Lang->LanguageName,
                      StringIdentifier->StringName,
                      NULL, // scope
                      NULL,
                      NULL
                      );
      } else {
        //
        // Find a matching string
        //
        CurrString = StringDBFindString (
                      Lang->LanguageName,
                      StringIdentifier->StringName,
                      NULL, // scope
                      NULL, // LanguagesOfInterest,
                      NULL
                      );
        //
        // IndirectionList);
        //
        if (NULL == CurrString) {
          CurrString = StringDBFindString (
                        L"eng",
                        StringIdentifier->StringName,
                        NULL, // scope
                        NULL, // LanguagesOfInterest,
                        NULL
                        );
          //
          // IndirectionList);
          //
        }

        EmptyString.LanguageName = Lang->LanguageName;
        if (CurrString == NULL) {
          CurrString        = &EmptyString;
          EmptyString.Flags = STRING_FLAGS_UNDEFINED;
        } else if ((StringIdentifier->Flags & STRING_FLAGS_REFERENCED) == 0) {
          CurrString        = &EmptyString;
          EmptyString.Flags = 0;
        }

        Offset += CurrString->Size;
      }
    }

    //
    // PASS 3: write the strings themselves.
    //
    Offset = sizeof (StringPack) + mDBData.NumStringIdentifiersReferenced * sizeof (STRING_OFFSET);
    for (StringIndex = 0; StringIndex < mDBData.NumStringIdentifiersReferenced; StringIndex++) {
      StringIdentifier = StringDBFindStringIdentifierByIndex (StringIndex);
      if (StringIdentifier == NULL) {
        Error (NULL, 0, 0, "internal error", "invalid string index 0x%X", StringIndex);
        return STATUS_ERROR;
      }
      //
      // For the first string (language name), we print out the "spacat" if they
      // requested it. We set LangName to point to the proper language name string above.
      //
      if (StringIndex == STRING_ID_LANGUAGE_NAME) {
        TempStringPtr = LangName;
      } else {
        //
        // Find a matching string if this string identifier was referenced
        //
        CurrString = NULL;
        if (StringIdentifier->Flags & STRING_FLAGS_REFERENCED) {
          CurrString = StringDBFindString (
                        Lang->LanguageName,
                        StringIdentifier->StringName,
                        NULL, // scope
                        NULL, // LanguagesOfInterest,
                        NULL
                        );
          //
          // IndirectionList);
          //
          if (NULL == CurrString) {
            CurrString = StringDBFindString (
                          L"eng",
                          StringIdentifier->StringName,
                          NULL, // scope
                          NULL, // LanguagesOfInterest,
                          NULL
                          );
            //
            // IndirectionList);
            //
          }
        }

        if (CurrString == NULL) {
          CurrString = &EmptyString;
        }

        TempStringPtr = CurrString->Str;
      }

      for (TempIndex = 0; TempStringPtr[TempIndex] != 0; TempIndex++) {
        fwrite (&TempStringPtr[TempIndex], sizeof (CHAR16), 1, Fptr);
        Offset += 2;
      }
      //
      // Print NULL WCHAR at the end of this string.
      //
      TempIndex = 0;
      fwrite (&TempIndex, sizeof (CHAR16), 1, Fptr);
      Offset += 2;
    }
    //
    // Sanity check the offset. Make sure our running offset is what we put in the
    // string pack header.
    //
    if (StringPack.Header.Length != Offset) {
      Error (
        __FILE__,
        __LINE__,
        0,
        "application error",
        "stringpack size 0x%X does not match final size 0x%X",
        StringPack.Header.Length,
        Offset
        );
    }
  }
  //
  // Print terminator string pack, closing brace and close the file.
  // The size of 0 triggers to the consumer that this is the end.
  //
  memset ((char *) &StringPack, 0, sizeof (EFI_HII_STRING_PACK_HEADER));
  StringPack.Header.Type = EFI_HII_STRING;
  fwrite ((void *) &StringPack, sizeof (StringPack), 1, Fptr);
  fclose (Fptr);
  return STATUS_SUCCESS;
}
